#include <std.h>
#include <error.h>
#include <type.h>
#include <dint.h>
#include <biosdisk.h>
#include <memory.h>
#include <asm.h>
#include <pio.h>

#define SECTORSIZE              512

#define HDC0_IOBASE		0x01F0
#define HDC1_IOBASE		0x0170

#define HD0_DRVSEL              0xA0
#define HD1_DRVSEL              0xB0

//
// Controller registers
//

#define HDC_DATA		0x0000
#define HDC_ERR			0x0001
#define HDC_FEATURE		0x0001
#define HDC_SECTORCNT		0x0002
#define HDC_SECTOR		0x0003
#define HDC_TRACKLSB		0x0004
#define HDC_TRACKMSB		0x0005
#define HDC_DRVHD		0x0006
#define HDC_STATUS		0x0007
#define HDC_COMMAND		0x0007
#define HDC_DEVCTRL		0x0008
#define HDC_ALT_STATUS		0x0206
#define HDC_CONTROL		0x0206

//
// Controller commands
//

#define HDCTL_RST		0x04   // Controller reset
#define HDCTL_4BITHD		0x08   // Use 4 bits for head id

//
// Drive commands
//

#define HDCMD_NULL		0x00
#define HDCMD_IDENTIFY		0xEC
#define HDCMD_RESET		0x10
#define HDCMD_READ		0x20
#define HDCMD_WRITE		0x30
#define HDCMD_READ_DMA          0xC8
#define HDCMD_WRITE_DMA         0xCA

//
// Controller status
//

#define HDCS_ERR		0x01   // Error
#define HDCS_IDX		0x02   // Index
#define HDCS_CORR		0x04   // Corrected data
#define HDCS_DRQ		0x08   // Data request
#define HDCS_DSC		0x10   // Drive seek complete
#define HDCS_DWF		0x20   // Drive write fault
#define HDCS_DRDY		0x40   // Drive ready
#define HDCS_BSY		0x80   // Controller busy

//
// Device control 
//

#define HDDC_HD15               0x08  // Bit should always be set to one
#define HDDC_SRST               0x04  // Soft reset
#define HDDC_NIEN               0x02  // Disable interrupts

//
// Controller error conditions 
//

#define HDCE_AMNF		0x01   // Address mark not found
#define HDCE_TK0NF		0x02   // Track 0 not found
#define HDCE_ABRT		0x04   // Abort
#define HDCE_MCR		0x08   // Media change requested
#define HDCE_IDNF		0x10   // Sector id not found
#define HDCE_MC			0x20   // Media change
#define HDCE_UNC		0x40   // Uncorrectable data error
#define HDCE_BBK		0x80   // Bad block

//
// Parameters returned by read drive parameters command

struct hdparam 
{
  u16 config;	       // General configuration bits
  u16 cylinders;	       // Cylinders
  u16 reserved;
  u16 heads;		       // Heads
  u16 unfbytespertrk;       // Unformatted bytes/track
  u16 unfbytes;	       // Unformatted bytes/sector
  u16 sectors;	       // Sectors per track
  u16 vendorunique[3];
  u8 serial[20];		       // Serial number
  u16 buffertype;	       // Buffer type
  u16 buffersize;	       // Buffer size, in 512-byte units
  u16 necc;		       // ECC bytes appended
  u8 rev[8];		               // Firmware revision
  u8 model[40];		       // Model name
  u8 nsecperint;	       // Sectors per interrupt
  u8 resv0;                 // Reserved
  u16 usedmovsd;	       // Can use double word read/write?
  u16 caps;                 // Capabilities
  u16 resv1;                // Reserved
  u16 pio;                  // PIO data transfer cycle timing (0=slow, 1=medium, 2=fast)
  u16 dma;                  // DMA data transfer cycle timing (0=slow, 1=medium, 2=fast)
  u16 valid;                // Flag valid fields to follow
  u16 logcyl;               // Logical cylinders
  u16 loghead;              // Logical heads
  u16 logspt;               // Logical sector/track
  u16 cap0;                 // Capacity in sectors (32-bit)
  u16 cap1;
  u16 multisec;             // Multiple sector values
  u16 totalsec0;            // Total number of user sectors
  u16 totalsec1;            //  (LBA; 32-bit value)
  u16 dmasingle;            // Single-word DMA info
  u16 dmamulti;             // Multi-word DMA info
  u16 piomode;              // Advanced PIO modes
  u16 minmulti;             // Minimum multiword xfer
  u16 multitime;            // Recommended cycle time
  u16 minpio;               // Min PIO without flow ctl
  u16 miniodry;             // Min with IORDY flow
  u16 resv2[6];             // Reserved
  u16 queue_depth;          // Queue depth
  u16 resv3[4];             // Reserved
  u16 vermajor;             // Major version number
  u16 verminor;             // Minor version number
  u16 cmdset1;              // Command set supported
  u16 cmdset2;
  u16 cfsse;		       // Command set-feature supported extensions
  u16 cfs_enable_1;	       // Command set-feature enabled
  u16 cfs_enable_2;	       // Command set-feature enabled
  u16 csf_default;	       // Command set-feature default
  u16 dmaultra;	       // UltraDMA mode (0:5 = supported mode, 8:13 = selected mode)

  u16 word89;	       // Reserved (word 89)
  u16 word90;	       // Reserved (word 90)
  u16 curapmvalues;	       // Current APM values
  u16 word92;	       // Reserved (word 92)
  u16 hw_config;	       // Hardware config
  u16 words94_125[32];      // Reserved words 94-125
  u16 last_lun; 	       // Reserved (word 126)
  u16 word127;	       // Reserved (word 127)
  u16 dlf;		       // Device lock function
				       // 15:9	reserved
				       // 8	security level 1:max 0:high
				       // 7:6	reserved
				       // 5	enhanced erase
				       // 4	expire
				       // 3	frozen
				       // 2	locked
				       // 1	en/disabled
				       // 0	capability
					
  u16 csfo;		       // Current set features options
				       // 15:4 reserved
				       // 3	 auto reassign
				       // 2	 reverting
				       // 1	 read-look-ahead
				       // 0	 write cache
					 
  u16 words130_155[26];     // Reserved vendor words 130-155
  u16 word156;
  u16 words157_159[3];      // Reserved vendor words 157-159
  u16 words160_255[96];     // Reserved words 160-255
}__attribute__((packed));

struct hd 
{
  int iobase;	                        // I/O port registers base address
  struct hdparam param;		        // Drive parameter block
  int drvsel;                           // Drive select on controller
  int status;                           // Controller status

  // Geometry
  unsigned int blks;		        // Number of blocks on drive
  unsigned int size;		        // Size in MB
  int lba;                              // Use LBA mode

  unsigned int cyls;	                // Number of cylinders
  unsigned int heads;		        // Number of heads
  unsigned int sectors;                 // Sectors per track
};

static struct hd hd;

static void hd_error(char *func, unsigned char error)
{
  printk("%s: ", func);
  if (error & HDCE_BBK)   printk("bad block  ");
  if (error & HDCE_UNC)   printk("uncorrectable data  ");
  if (error & HDCE_MC)    printk("media change  ");
  if (error & HDCE_IDNF)  printk("id not found  ");
  if (error & HDCE_MCR)   printk("media change requested  ");
  if (error & HDCE_ABRT)  printk("abort  ");
  if (error & HDCE_TK0NF) printk("track 0 not found  ");
  if (error & HDCE_AMNF)  printk("address mark not found  ");
  printk("\n");
}

static int hd_wait(unsigned char mask)
{
  unsigned char status;

  while (1)
  {
    status = inb_p((unsigned short) (hd.iobase + HDC_ALT_STATUS));
    if (status & HDCS_ERR) 
    {
       unsigned char error;
 
       error = inb_p((unsigned short) (hd.iobase + HDC_ERR));
       hd_error("hd_wait", error);

       return error;
    }

    if (!(status & HDCS_BSY) && ((status & mask) == mask)) return 0;
  }
}

static void hd_setup_transfer(u32 blkno, int nsects)
{
  unsigned int track;
  unsigned int head;
  unsigned int sector;

  if (hd.lba)
  {
    track = (blkno >> 8) & 0xFFFF;
    head = ((blkno >> 24) & 0xF) | 0x40;
    sector = blkno & 0xFF;
  }
  else 
  {
    track = blkno / (hd.heads * hd.sectors);
    head = (blkno / hd.sectors) % hd.heads;
    sector = blkno % hd.sectors + 1;
  }

  outb_p((unsigned short) (hd.iobase + HDC_SECTORCNT), nsects);
  outb_p((unsigned short) (hd.iobase + HDC_SECTOR), (unsigned char) sector);
  outb_p((unsigned short) (hd.iobase + HDC_TRACKLSB), (unsigned char) track);
  outb_p((unsigned short) (hd.iobase + HDC_TRACKMSB), (unsigned char) (track >> 8));
  outb_p((unsigned short) (hd.iobase + HDC_DRVHD), (unsigned char) (head | hd.drvsel));
}

static int hd_identify()
{
  int result;

  // Issue read drive parameters command
  outb_p(hd.iobase + HDC_DRVHD, hd.drvsel);
  outb_p(hd.iobase + HDC_COMMAND, HDCMD_IDENTIFY);

  // Wait for data ready
  if (!(inb_p((unsigned short) (hd.iobase + HDC_ALT_STATUS)) & HDCS_DRQ))
  {
    result = hd_wait(HDCS_DRQ);
    if (result != 0) return result;
  }

  // Read parameter data
  inpstr(hd.iobase + HDC_DATA, &hd.param, SECTORSIZE / 2);

  // Fill in drive parameters
  hd.cyls = hd.param.cylinders;
  hd.heads = hd.param.heads;
  hd.sectors = hd.param.sectors;

  // Determine LBA or CHS mode
  if (hd.param.totalsec0 == 0 && hd.param.totalsec1 == 0)
  {
    hd.lba = 0;
    hd.blks = hd.cyls * hd.heads * hd.sectors;
  }
  else
  {
    hd.lba = 1;
    hd.blks = (hd.param.totalsec1 << 16) | hd.param.totalsec0;
  }
  hd.size = hd.blks / ((1024 * 1024) / SECTORSIZE);

  // Read status
  hd.status = inb_p((unsigned short) (hd.iobase + HDC_STATUS));

  return 0;
}

int hd_read(void *buffer,u32 blkno,int count)
{
  char *hdbuf;
  int i;
  int result;
  int nsects;

  if (count == 0) return 0;
  nsects = count / SECTORSIZE;

  // Issue read sectors command
  result = hd_wait(HDCS_DRDY);
  if (result != 0) 
  {
    printk("hd_read: no drdy in wait (%d)\n", result);
    return -1;
  }

  hd_setup_transfer(blkno, nsects);
  outb_p((unsigned short) (hd.iobase + HDC_COMMAND), HDCMD_READ);

  hdbuf = (char *) buffer;
  for (i = 0; i < nsects; i++) 
  {
    // Wait for data ready
    if (!(inb_p((unsigned short)(hd.iobase + HDC_ALT_STATUS)) & HDCS_DRQ))
    {
      result = hd_wait(HDCS_DRQ);
      if (result != 0)
      {
	printk("hd_read: no drq (%d)\n", result);
        return -1;
      }
    }

    // Check status
    hd.status = inb_p((unsigned short) (hd.iobase + HDC_STATUS));
    if (hd.status & HDCS_ERR)
    {
      printk("hd_read: read error (%d)\n", hd.status);
      return -1;
    }

    // Read sector data
    inpstr(hd.iobase + HDC_DATA, hdbuf, SECTORSIZE / 2);
    hdbuf += SECTORSIZE;
  }

  return count;
}


int init_hdata()
{
	memset(&hd, 0, sizeof(struct hd));
	hd.iobase = HDC0_IOBASE;
	hd.drvsel = HD0_DRVSEL;
	hd_identify();
	outb_p((unsigned short) (hd.iobase + HDC_CONTROL), HDDC_HD15 | HDDC_NIEN);
	printk("%d: %u blks (%d MB) CHS=%u/%u/%u", hd.drvsel, hd.blks, hd.size, hd.cyls, hd.heads, hd.sectors);
	return NULL;
}

kdrv hdata={
	.name="hdata",
	.init=init_hdata,
	.reset=NULL,
	.deinit=NULL, 
	.read_device=NULL,
	.write_device=NULL,
	.device_irq=DEVICE_IRQ_UNUSED,
};

__init(hdata_init,hdata);
